<template>
  <div
    v-if="!ranged"
    :class="wrapClasses"
    v-click-outside="handleClose"
    ref="reference">
    <div v-if="$slots.prepend" :class="[prefixCls + '-group-prepend']">
      <slot name="prepend"></slot>
    </div>
    <d-input
      :class="inputClasses"
      :readonly="!editable || readonly || type === 'dates'"
      :disabled="pickerDisabled"
      :size="pickerSize"
      :name="name"
      v-bind="firstInputId"
      :placeholder="placeholder"
      @on-focus="handleFocus"
      @keydown.native="handleKeydown"
      :value="displayValue"
      @input="value => userInput = value"
      @on-change="handleChange"
      @mouseenter.native="handleMouseEnter"
      @mouseleave.native="showClose = false"
      :validateEvent="false">
      <Icon slot="prefix"
            :name="triggerClass"
            :class="[clsPrefix + 'input__icon']"
            @click="handleFocus">
      </Icon>
      <Icon slot="suffix"
            :name="showClose ? '' + clearIcon : ''"
            :class="[clsPrefix + 'input__icon']"
            @click="handleClickIcon"
            v-if="haveTrigger">
      </Icon>
    </d-input>
    <div v-if="$slots.append" :class="[prefixCls + '-group-append']">
      <slot name="append"></slot>
    </div>
  </div>
  <div
    :class="wrapClasses"
    @click="handleRangeClick"
    @mouseenter="handleMouseEnter"
    @mouseleave="showClose = false"
    @keydown="handleKeydown"
    ref="reference"
    v-click-outside="handleClose"
    v-else>
    <div v-if="$slots.prepend" slot="prepend" :class="[prefixCls + '-group-prepend']">
      <slot name="prepend"></slot>
    </div>
    <div :class="innerClasses">
      <Icon :class="[clsPrefix + 'input__icon', clsPrefix + 'range__icon']" :name="triggerClass"></Icon>
      <input
        autocomplete="off"
        :placeholder="startPlaceholder"
        :value="displayValue && displayValue[0]"
        :disabled="pickerDisabled"
        v-bind="firstInputId"
        :readonly="!editable || readonly"
        :name="name && name[0]"
        @input="handleStartInput"
        @change="handleStartChange"
        @focus="handleFocus"
        :class="[clsPrefix + 'range-input']">
      <span :class="[clsPrefix + 'range-separator']">{{ rangeSeparator }}</span>
      <input
        autocomplete="off"
        :placeholder="endPlaceholder"
        :value="displayValue && displayValue[1]"
        :disabled="pickerDisabled"
        v-bind="secondInputId"
        :readonly="!editable || readonly"
        :name="name && name[1]"
        @input="handleEndInput"
        @change="handleEndChange"
        @focus="handleFocus"
        :class="[clsPrefix + 'range-input']">
      <Icon
        @click="handleClickIcon"
        v-if="haveTrigger"
        :name="showClose ? '' + clearIcon : ''"
        :class="[clsPrefix + 'input__icon', clsPrefix + 'range__close-icon']">
      </Icon>
    </div>
    <div v-if="$slots.append" slot="append" :class="[prefixCls + '-group-append']">
      <slot name="append"></slot>
    </div>
  </div>
</template>

<script>
import Vue from 'vue'
import Config from '../../config'
import Icon from '../icon'
import DInput from '../input/input.vue'
import {directive as ClickOutside} from 'v-click-outside-x'
import { formatDate, parseDate, isDateObject, getWeekNumber } from './util'
import Popper from '../../utils/vue-popper'
import Emitter from '../../mixins/emitter'
import { oneOf, merge } from '../../utils/assist'

const NewPopper = {
  props: {
    appendToBody: Popper.props.appendToBody,
    offset: Popper.props.offset,
    boundariesPadding: Popper.props.boundariesPadding,
    arrowOffset: Popper.props.arrowOffset
  },
  methods: Popper.methods,
  data () {
    return merge({ visibleArrow: true }, Popper.data)
  },
  beforeDestroy: Popper.beforeDestroy
}
const clsPrefix = Config.clsPrefix
const prefixCls = `${clsPrefix}date-editor`
const DEFAULT_FORMATS = {
  date: 'yyyy-MM-dd',
  month: 'yyyy-MM',
  datetime: 'yyyy-MM-dd HH:mm:ss',
  time: 'HH:mm:ss',
  week: 'yyyywWW',
  timerange: 'HH:mm:ss',
  daterange: 'yyyy-MM-dd',
  datetimerange: 'yyyy-MM-dd HH:mm:ss',
  year: 'yyyy'
}
const HAVE_TRIGGER_TYPES = [
  'date',
  'datetime',
  'time',
  'time-select',
  'week',
  'month',
  'year',
  'daterange',
  'timerange',
  'datetimerange',
  'dates'
]
const DATE_FORMATTER = function (value, format) {
  if (format === 'timestamp') return value.getTime()
  return formatDate(value, format)
}
const DATE_PARSER = function (text, format) {
  if (format === 'timestamp') return new Date(Number(text))
  return parseDate(text, format)
}
const RANGE_FORMATTER = function (value, format) {
  if (Array.isArray(value) && value.length === 2) {
    const start = value[0]
    const end = value[1]

    if (start && end) {
      return [DATE_FORMATTER(start, format), DATE_FORMATTER(end, format)]
    }
  }
  return ''
}
const RANGE_PARSER = function (array, format, separator) {
  if (!Array.isArray(array)) {
    array = array.split(separator)
  }
  if (array.length === 2) {
    const range1 = array[0]
    const range2 = array[1]

    return [DATE_PARSER(range1, format), DATE_PARSER(range2, format)]
  }
  return []
}
const TYPE_VALUE_RESOLVER_MAP = {
  default: {
    formatter (value) {
      if (!value) return ''
      return '' + value
    },
    parser (text) {
      if (text === undefined || text === '') return null
      return text
    }
  },
  week: {
    formatter (value, format) {
      let week = getWeekNumber(value)
      let month = value.getMonth()
      const trueDate = new Date(value)
      if (week === 1 && month === 11) {
        trueDate.setHours(0, 0, 0, 0)
        trueDate.setDate(trueDate.getDate() + 3 - (trueDate.getDay() + 6) % 7)
      }
      let date = formatDate(trueDate, format)

      date = /WW/.test(date)
        ? date.replace(/WW/, week < 10 ? '0' + week : week)
        : date.replace(/W/, week)
      return date
    },
    parser (text) {
      const array = (text || '').split('w')
      if (array.length === 2) {
        const year = Number(array[0])
        const month = Number(array[1])

        if (!isNaN(year) && !isNaN(month) && month < 54) {
          return text
        }
      }
      return null
    }
  },
  date: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  datetime: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  daterange: {
    formatter: RANGE_FORMATTER,
    parser: RANGE_PARSER
  },
  datetimerange: {
    formatter: RANGE_FORMATTER,
    parser: RANGE_PARSER
  },
  timerange: {
    formatter: RANGE_FORMATTER,
    parser: RANGE_PARSER
  },
  time: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  month: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  year: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  number: {
    formatter (value) {
      if (!value) return ''
      return '' + value
    },
    parser (text) {
      let result = Number(text)

      if (!isNaN(text)) {
        return result
      } else {
        return null
      }
    }
  },
  dates: {
    formatter (value, format) {
      return value.map(date => DATE_FORMATTER(date, format))
    },
    parser (value, format) {
      return (typeof value === 'string' ? value.split(', ') : value)
        .map(date => date instanceof Date ? date : DATE_PARSER(date, format))
    }
  }
}
const PLACEMENT_MAP = {
  left: 'bottom-start',
  center: 'bottom',
  right: 'bottom-end'
}

const parseAsFormatAndType = (value, customFormat, type, rangeSeparator = '-') => {
  if (!value) return null
  const parser = (
    TYPE_VALUE_RESOLVER_MAP[type] ||
      TYPE_VALUE_RESOLVER_MAP['default']
  ).parser
  const format = customFormat || DEFAULT_FORMATS[type]
  return parser(value, format, rangeSeparator)
}

const formatAsFormatAndType = (value, customFormat, type) => {
  if (!value) return null
  const formatter = (
    TYPE_VALUE_RESOLVER_MAP[type] ||
      TYPE_VALUE_RESOLVER_MAP['default']
  ).formatter
  const format = customFormat || DEFAULT_FORMATS[type]
  return formatter(value, format)
}

/*
   * Considers:
   *   1. Date object
   *   2. date string
   *   3. array of 1 or 2
   */
const valueEquals = function (a, b) {
  // considers Date object and string
  const dateEquals = function (a, b) {
    const aIsDate = a instanceof Date
    const bIsDate = b instanceof Date
    if (aIsDate && bIsDate) {
      return a.getTime() === b.getTime()
    }
    if (!aIsDate && !bIsDate) {
      return a === b
    }
    return false
  }

  const aIsArray = a instanceof Array
  const bIsArray = b instanceof Array
  if (aIsArray && bIsArray) {
    if (a.length !== b.length) {
      return false
    }
    return a.every((item, index) => dateEquals(item, b[index]))
  }
  if (!aIsArray && !bIsArray) {
    return dateEquals(a, b)
  }
  return false
}

const isString = function (val) {
  return typeof val === 'string' || val instanceof String
}

const validator = function (val) {
  // either: String, Array of String, null / undefined
  return (
    val === null ||
      val === undefined ||
      isString(val) ||
      (Array.isArray(val) && val.length === 2 && val.every(isString))
  )
}

export default {
  mixins: [Emitter, NewPopper],
  inject: {
    duiForm: {
      default: ''
    },
    lbuanFormItem: {
      default: ''
    }
  },
  props: {
    size: {
      validator (value) {
        return oneOf(value, ['small', 'large', 'default'])
      },
      default () {
        return this.$DUI.size === '' ? 'default' : this.$DUI.size
      }
    },
    format: String,
    valueFormat: String,
    readonly: Boolean,
    placeholder: String,
    startPlaceholder: String,
    endPlaceholder: String,
    prefixIcon: String,
    clearIcon: {
      type: String,
      default: 'x-circle'
    },
    name: {
      default: '',
      validator
    },
    disabled: Boolean,
    clearable: {
      type: Boolean,
      default: true
    },
    id: {
      default: '',
      validator
    },
    popperClass: String,
    editable: {
      type: Boolean,
      default: true
    },
    align: {
      type: String,
      default: 'left'
    },
    value: {},
    defaultValue: {},
    defaultTime: {},
    rangeSeparator: {
      default: '-'
    },
    pickerOptions: {},
    unlinkPanels: Boolean
  },

  components: { Icon, DInput },

  directives: { ClickOutside },

  data () {
    return {
      clsPrefix: clsPrefix,
      prefixCls: prefixCls,
      pickerVisible: false,
      showClose: false,
      userInput: null,
      valueOnOpen: null, // value when picker opens, used to determine whether to emit change
      unwatchPickerOptions: null
    }
  },

  watch: {
    pickerVisible (val) {
      if (this.readonly || this.pickerDisabled) return
      if (val) {
        this.showPicker()
        this.valueOnOpen = Array.isArray(this.value) ? [...this.value] : this.value
      } else {
        this.hidePicker()
        this.emitChange(this.value)
        this.userInput = null
        this.$emit('blur', this)
        this.blur()
      }
    },
    parsedValue: {
      immediate: true,
      handler (val) {
        if (this.picker) {
          this.picker.value = val
        }
      }
    },
    defaultValue (val) {
      // NOTE: should eventually move to jsx style picker + panel ?
      if (this.picker) {
        this.picker.defaultValue = val
      }
    }
  },

  computed: {
    inputClasses () {
      return [
        `${prefixCls}`,
        `${prefixCls}--` + this.type
      ]
    },

    wrapClasses () {
      return [
        `${prefixCls}`,
        {
          [`${prefixCls}-group`]: (this.$slots.prepend || this.$slots.append)
        }
      ]
    },

    innerClasses () {
      return [
        `${clsPrefix}range-editor`,
        `${clsPrefix}input__inner`,
        `${prefixCls}--` + this.type,
        {
          [`${clsPrefix}range-editor--${this.pickerSize}`]: this.pickerSize,
          [`is-disabled`]: this.pickerDisabled,
          [`is-active`]: this.pickerVisible
        }
      ]
    },

    ranged () {
      return this.type.indexOf('range') > -1
    },

    reference () {
      const reference = this.$refs.reference
      return reference.$el || reference
    },

    refInput () {
      if (this.reference) {
        return [].slice.call(this.reference.querySelectorAll('input'))
      }
      return []
    },

    valueIsEmpty () {
      const val = this.value
      if (Array.isArray(val)) {
        for (let i = 0, len = val.length; i < len; i++) {
          if (val[i]) {
            return false
          }
        }
      } else {
        if (val) {
          return false
        }
      }
      return true
    },

    triggerClass () {
      return this.prefixIcon || (this.type.indexOf('time') !== -1 ? 'clock' : 'calendar')
    },

    selectionMode () {
      if (this.type === 'week') {
        return 'week'
      } else if (this.type === 'month') {
        return 'month'
      } else if (this.type === 'year') {
        return 'year'
      } else if (this.type === 'dates') {
        return 'dates'
      }

      return 'day'
    },

    haveTrigger () {
      if (typeof this.showTrigger !== 'undefined') {
        return this.showTrigger
      }
      return HAVE_TRIGGER_TYPES.indexOf(this.type) !== -1
    },

    displayValue () {
      const formattedValue = formatAsFormatAndType(this.parsedValue, this.format, this.type, this.rangeSeparator)
      if (Array.isArray(this.userInput)) {
        return [
          this.userInput[0] || (formattedValue && formattedValue[0]) || '',
          this.userInput[1] || (formattedValue && formattedValue[1]) || ''
        ]
      } else if (this.userInput !== null) {
        return this.userInput
      } else if (formattedValue) {
        return this.type === 'dates'
          ? formattedValue.join(', ')
          : formattedValue
      } else {
        return ''
      }
    },

    parsedValue () {
      if (!this.value) return this.value // component value is not set
      if (this.type === 'time-select') return this.value // time-select does not require parsing, this might change in next major version

      const valueIsDateObject = isDateObject(this.value) || (Array.isArray(this.value) && this.value.every(isDateObject))
      if (valueIsDateObject) {
        return this.value
      }

      if (this.valueFormat) {
        return parseAsFormatAndType(this.value, this.valueFormat, this.type, this.rangeSeparator) || this.value
      }

      // NOTE: deal with common but incorrect usage, should remove in next major version
      // user might provide string / timestamp without value-format, coerce them into date (or array of date)
      return Array.isArray(this.value) ? this.value.map(val => new Date(val)) : new Date(this.value)
    },

    firstInputId () {
      const obj = {}
      let id
      if (this.ranged) {
        id = this.id && this.id[0]
      } else {
        id = this.id
      }
      if (id) obj.id = id
      return obj
    },

    secondInputId () {
      const obj = {}
      let id
      if (this.ranged) {
        id = this.id && this.id[1]
      }
      if (id) obj.id = id
      return obj
    },

    _duiFormItemSize () {
      return (this.duiFormItem || {}).duiFormItemSize
    },

    pickerSize () {
      return this.size || this._duiFormItemSize || (this.$DUI || {}).size
    },

    pickerDisabled () {
      return this.disabled || (this.duiForm || {}).disabled
    }
  },

  created () {
    // vue-popper
    this.popperOptions = {
      boundariesPadding: 0,
      gpuAcceleration: false
    }
    this.placement = PLACEMENT_MAP[this.align] || PLACEMENT_MAP.left

    this.$on('fieldReset', this.handleFieldReset)
  },

  methods: {
    focus () {
      if (!this.ranged) {
        this.$refs.reference.focus()
      } else {
        this.handleFocus()
      }
    },

    blur () {
      this.refInput.forEach(input => input.blur())
    },

    // {parse, formatTo} Value deals maps component value with internal Date
    parseValue (value) {
      const isParsed = isDateObject(value) || (Array.isArray(value) && value.every(isDateObject))
      if (this.valueFormat && !isParsed) {
        return parseAsFormatAndType(value, this.valueFormat, this.type, this.rangeSeparator) || value
      } else {
        return value
      }
    },

    formatToValue (date) {
      const isFormattable = isDateObject(date) || (Array.isArray(date) && date.every(isDateObject))
      if (this.valueFormat && isFormattable) {
        return formatAsFormatAndType(date, this.valueFormat, this.type, this.rangeSeparator)
      } else {
        return date
      }
    },

    // {parse, formatTo} String deals with user input
    parseString (value) {
      const type = Array.isArray(value) ? this.type : this.type.replace('range', '')
      return parseAsFormatAndType(value, this.format, type)
    },

    formatToString (value) {
      const type = Array.isArray(value) ? this.type : this.type.replace('range', '')
      return formatAsFormatAndType(value, this.format, type)
    },

    handleMouseEnter () {
      if (this.readonly || this.pickerDisabled) return
      if (!this.valueIsEmpty && this.clearable) {
        this.showClose = true
      }
    },

    handleChange () {
      if (this.userInput) {
        const value = this.parseString(this.displayValue)
        if (value) {
          this.picker.value = value
          if (this.isValidValue(value)) {
            this.emitInput(value)
            this.userInput = null
          }
        }
      }
      if (this.userInput === '') {
        this.emitInput(null)
        this.emitChange(null)
        this.userInput = null
      }
    },

    handleStartInput (event) {
      if (this.userInput) {
        this.userInput = [event.target.value, this.userInput[1]]
      } else {
        this.userInput = [event.target.value, null]
      }
    },

    handleEndInput (event) {
      if (this.userInput) {
        this.userInput = [this.userInput[0], event.target.value]
      } else {
        this.userInput = [null, event.target.value]
      }
    },

    handleStartChange (event) {
      const value = this.parseString(this.userInput && this.userInput[0])
      if (value) {
        this.userInput = [this.formatToString(value), this.displayValue[1]]
        const newValue = [value, this.picker.value && this.picker.value[1]]
        this.picker.value = newValue
        if (this.isValidValue(newValue)) {
          this.emitInput(newValue)
          this.userInput = null
        }
      }
    },

    handleEndChange (event) {
      const value = this.parseString(this.userInput && this.userInput[1])
      if (value) {
        this.userInput = [this.displayValue[0], this.formatToString(value)]
        const newValue = [this.picker.value && this.picker.value[0], value]
        this.picker.value = newValue
        if (this.isValidValue(newValue)) {
          this.emitInput(newValue)
          this.userInput = null
        }
      }
    },

    handleClickIcon (event) {
      if (this.readonly || this.pickerDisabled) return
      if (this.showClose) {
        this.valueOnOpen = this.value
        event.stopPropagation()
        this.emitInput(null)
        this.emitChange(null)
        this.showClose = false
        if (this.picker && typeof this.picker.handleClear === 'function') {
          this.picker.handleClear()
        }
      } else {
        this.pickerVisible = !this.pickerVisible
      }
    },

    handleClose () {
      if (!this.pickerVisible) return
      this.pickerVisible = false

      if (this.type === 'dates') {
        // restore to former value
        const oldValue = parseAsFormatAndType(this.valueOnOpen, this.valueFormat, this.type, this.rangeSeparator) || this.valueOnOpen
        this.emitInput(oldValue)
      }
    },

    handleFieldReset (initialValue) {
      this.userInput = initialValue === '' ? null : initialValue
    },

    handleFocus () {
      const type = this.type

      if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
        this.pickerVisible = true
      }
      this.$emit('focus', this)
    },

    handleKeydown (event) {
      const keyCode = event.keyCode

      // ESC
      if (keyCode === 27) {
        this.pickerVisible = false
        event.stopPropagation()
        return
      }

      // Tab
      if (keyCode === 9) {
        if (!this.ranged) {
          this.handleChange()
          this.pickerVisible = this.picker.visible = false
          this.blur()
          event.stopPropagation()
        } else {
          // user may change focus between two input
          setTimeout(() => {
            if (this.refInput.indexOf(document.activeElement) === -1) {
              this.pickerVisible = false
              this.blur()
              event.stopPropagation()
            }
          }, 0)
        }
        return
      }

      // Enter
      if (keyCode === 13) {
        if (this.userInput === '' || this.isValidValue(this.parseString(this.displayValue))) {
          this.handleChange()
          this.pickerVisible = this.picker.visible = false
          this.blur()
        }
        event.stopPropagation()
        return
      }

      // if user is typing, do not let picker handle key input
      if (this.userInput) {
        event.stopPropagation()
        return
      }

      // delegate other keys to panel
      if (this.picker && this.picker.handleKeydown) {
        this.picker.handleKeydown(event)
      }
    },

    handleRangeClick () {
      const type = this.type

      if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
        this.pickerVisible = true
      }
      this.$emit('focus', this)
    },

    hidePicker () {
      if (this.picker) {
        this.picker.resetView && this.picker.resetView()
        this.pickerVisible = this.picker.visible = false
        this.destroyPopper()
      }
    },

    showPicker () {
      if (this.$isServer) return
      if (!this.picker) {
        this.mountPicker()
      }
      this.pickerVisible = this.picker.visible = true

      this.updatePopper()

      this.picker.value = this.parsedValue
      this.picker.resetView && this.picker.resetView()

      this.$nextTick(() => {
        this.picker.adjustSpinners && this.picker.adjustSpinners()
      })
    },

    mountPicker () {
      this.picker = new Vue(this.panel).$mount()
      this.picker.defaultValue = this.defaultValue
      this.picker.defaultTime = this.defaultTime
      this.picker.popperClass = this.popperClass
      this.popperElm = this.picker.$el
      this.picker.width = this.reference.getBoundingClientRect().width
      this.picker.showTime = this.type === 'datetime' || this.type === 'datetimerange'
      this.picker.selectionMode = this.selectionMode
      this.picker.unlinkPanels = this.unlinkPanels
      this.picker.arrowControl = this.arrowControl || this.timeArrowControl || false
      this.$watch('format', (format) => {
        this.picker.format = format
      })

      const updateOptions = () => {
        const options = this.pickerOptions

        if (options && options.selectableRange) {
          let ranges = options.selectableRange
          const parser = TYPE_VALUE_RESOLVER_MAP.datetimerange.parser
          const format = DEFAULT_FORMATS.timerange

          ranges = Array.isArray(ranges) ? ranges : [ranges]
          this.picker.selectableRange = ranges.map(range => parser(range, format, this.rangeSeparator))
        }

        for (const option in options) {
          if (options.hasOwnProperty(option) &&
              // 忽略 time-picker 的该配置项
              option !== 'selectableRange') {
            this.picker[option] = options[option]
          }
        }

        // main format must prevail over undocumented pickerOptions.format
        if (this.format) {
          this.picker.format = this.format
        }
      }
      updateOptions()
      this.unwatchPickerOptions = this.$watch('pickerOptions', () => updateOptions(), { deep: true })

      this.$el.appendChild(this.picker.$el)
      this.picker.resetView && this.picker.resetView()

      this.picker.$on('dodestroy', this.doDestroy)
      this.picker.$on('pick', (date = '', visible = false) => {
        this.userInput = null
        this.pickerVisible = this.picker.visible = visible
        this.emitInput(date)
        this.picker.resetView && this.picker.resetView()
      })

      this.picker.$on('select-range', (start, end, pos) => {
        if (this.refInput.length === 0) return
        if (!pos || pos === 'min') {
          this.refInput[0].setSelectionRange(start, end)
          this.refInput[0].focus()
        } else if (pos === 'max') {
          this.refInput[1].setSelectionRange(start, end)
          this.refInput[1].focus()
        }
      })
    },

    unmountPicker () {
      if (this.picker) {
        this.picker.$destroy()
        this.picker.$off()
        if (typeof this.unwatchPickerOptions === 'function') {
          this.unwatchPickerOptions()
        }
        this.picker.$el.parentNode.removeChild(this.picker.$el)
      }
    },

    emitChange (val) {
      // determine user real change only
      if (!valueEquals(val, this.valueOnOpen)) {
        this.$emit('change', val)
        this.valueOnOpen = val
      }
    },

    emitInput (val) {
      const formatted = this.formatToValue(val)
      if (!valueEquals(this.value, formatted)) {
        this.$emit('input', formatted)
      }
    },

    isValidValue (value) {
      if (!this.picker) {
        this.mountPicker()
      }
      if (this.picker.isValidValue) {
        return value && this.picker.isValidValue(value)
      } else {
        return true
      }
    }
  }
}
</script>
